package com.smartalarmclock.Model;

import java.util.HashSet;

import com.smartalarmclock.Model.Exceptions.InvalidSaveDataEntityOperationException;

import android.content.Context;


final class MultipleReference<T extends IReferencedDataEntity> extends Reference<T> {

	// Members
	
	private IReferencingMultipleDataEntity m_ReferencingEntity;
	
	private HashSet<T> m_CurrentReferences;
	private boolean m_AreReferencesLoaded;
	
	// Constructors
	
	/**
	 * Constructor
	 * @param referencingEntity the referencing entity
	 * @param referencedType the type of the reference (the type in the DB)
	 * @param deleteMethod the method to use when the referencing entity is deleted
	 * (the delete method should be set to delete if each reference is referenced by one referencing entity only)
	 */
	public MultipleReference(
		IReferencingMultipleDataEntity referencingEntity,
		Class<?> referencedType,
		DeleteMethod deleteMethod) {
		super(referencedType, deleteMethod);
		m_ReferencingEntity = referencingEntity;
		
		m_CurrentReferences = new HashSet<T>();
		m_AreReferencesLoaded = false;
	}
	
	/**
	 * @param context the context of the request
	 * @return the referenced data entities
	 */
	public HashSet<T> GetReferences(Context context) {
		LoadReferences(context);
		return m_CurrentReferences;
	}
	
	/**
	 * Adds a new reference of the matching type.
	 * @param context the context of the request
	 * @param newReference the new referenced entity
	 */
	public void Add(Context context, T newReference) {
		LoadReferences(context);
		
		if (newReference != null) {
			HandleNewReference(newReference, m_ReferencingEntity);
			m_CurrentReferences.add(newReference);
		}
	}
	
	/**
	 * Removes a reference
	 * @param context the context of the request
	 * @param referenceToRemove the reference to remove
	 */
	public void Remove(Context context, T referenceToRemove) {
		LoadReferences(context);
		
		if (referenceToRemove != null) {
			m_CurrentReferences.remove(referenceToRemove);
			HandleOldReference(referenceToRemove, m_ReferencingEntity);
		}
	}
	
	/**
	 * Reloads the references from the DB.
	 * @param context the context of the request
	 */
	public void Refresh(Context context) {
		// Moving all references from the references list to the old references list
		// While reloading the references, the old references will be removed from the old references list,
		// if needed.
		while (!m_CurrentReferences.isEmpty()) {
			Remove(context, m_CurrentReferences.iterator().next());
		}
		
		m_AreReferencesLoaded = false;
		LoadReferences(context);
	}
	
	// Overriding Methods
	
	/**
	 * Saves the referenced entities.
	 * @param context the context of the request
	 * @throws InvalidSaveDataEntityOperationException if the save operation is invalid.
	 */
	@Override
	public void SaveReferences(Context context) throws InvalidSaveDataEntityOperationException {
		if (m_AreReferencesLoaded) {
			
			// Deleting the old references from the referencing table
			for (T oldReference : m_OldReferences) {
				m_ReferencingEntity.DeleteReferenceConnection(context, oldReference);
			}
			
			// Deleting the old referenced entities themselves if needed.
			DeleteAllOldReferences(context);
			
			// Saving the current referenced entities and saving them in the referencing table.
			for (T reference : m_CurrentReferences) {
				reference.Save(context);
				m_ReferencingEntity.SaveReferenceConnection(context, reference);
			}
			
			// Saving the reference mapping to the DB.
			SmartAlarmClockDB.GetInstance().AddReferenceMapping(context, m_ReferencingEntity.getClass(), m_referencedType);
		}
	}
	
	/**
	 * Deletes the referenced data entity from the DB only if the delete method is to delete
	 * (the delete method should be set to delete if each reference is referenced by one referencing entity only)
	 * @param context the context of the request
	 */
	@Override
	public void OnDeleteReferencingEntity(Context context) {
		LoadReferences(context);
		
		// Moving all references from the references list to the old references list.
		while (!m_CurrentReferences.isEmpty()) {
			Remove(context, m_CurrentReferences.iterator().next());
		}
		
		// Deleting the old references from the referencing table.
		for (T oldReference : m_OldReferences) {
			m_ReferencingEntity.DeleteReferenceConnection(context, oldReference);
		}
		
		// Deleting the references themselves if needed
		DeleteAllOldReferences(context);
	}
	
	// Private Methods
	
	/**
	 * Loads all references from the referencing table in the DB.
	 * @param context the context of the request
	 */
	private void LoadReferences(Context context) {
		if (!m_AreReferencesLoaded) {
			m_AreReferencesLoaded = true;
			m_ReferencingEntity.LoadReferences(context, m_referencedType);
		}
	}
}
